#include "gamma.h"
//
Gamma::Gamma(std::list<unsigned int*> *distancias) 
{
	this->distancias = distancias;
}
//
Gamma::Gamma(std::list<unsigned char*> *gammas) 
{
	this->distancias = new std::list <unsigned int*>();
	this->gammas = gammas;
}
/*Calcula la cantidad de bits disponibles en el ultimo byte de 
  un código unario*/
unsigned char calcularBitsDisponibles(unsigned char ultimo)
{
	if(ultimo == 0)
		return 7;
	else if(ultimo == 128)
		return 6;
	else if(ultimo == 192)
		return 5;
	else if(ultimo == 224)
		return 4;
	else if(ultimo == 240)
		return 3;
	else if(ultimo == 248)
		return 2;
	else if(ultimo == 252)
		return 1;
	else if(ultimo == 254)
		return 0;
	else
		return 9;//Error en Unario
}
/*Calcula la parte unaria que compone al gamma pasado por parámetro 
  (si hay mas de uno toma el primero)*/
unsigned char calcularParteUnaria(std::list<unsigned char*> *gamma)
{
	unsigned char unario = 0;
	std::list <unsigned char*>::iterator it;
	for(it = gamma->begin(); it != gamma->end(); it++)
	{
		if(**it == 255)
		{
			//si es el ultimo...
			std::list <unsigned char*>::iterator copia = it;
			it++;
			if(it == gamma->end())
				return 0;
			it = copia;
			unario += 8;
		}
		else if(**it == 254)
			unario += 8;
		else if((**it >> 1) == 126)
			unario += 7;
		else if((**it >> 2) == 62)
			unario += 6;
		else if((**it >> 3) == 30)
			unario += 5;
		else if((**it >> 4) == 14)
			unario += 4;
		else if((**it >> 5) == 6)
			unario += 3;
		else if((**it >> 6) == 2)
			unario += 2;
		else
			unario += 1;
		if(**it != 255)
			break;
	}
	return unario;	
}
/*Calcula la parte binaria que compone al gamma pasado por parámetro 
  (si hay mas de uno toma el primero)*/
unsigned int calcularParteBinaria(std::list<unsigned char*> *gamma)
{
	unsigned int parte_binaria = 0;
	unsigned char bits_unario = calcularParteUnaria(gamma);
	unsigned char bits_binario = bits_unario - 1;
	if(bits_binario > 0)
	{
		std::list<unsigned char*>::iterator it;
		for(it = gamma->begin(); it != gamma->end(); it++)
		{
			if(bits_unario >= 8)
			{
				bits_unario -= 8;
			}
			else
			{
				unsigned char aux = **it;
				aux <<= bits_unario;
				aux >>= bits_unario;
				for(int i = 0; i < (8 - bits_unario); i++)
				{
					if(bits_binario > 0)
					{
						parte_binaria += (aux >> (7 - bits_unario - i)) * pow(2, bits_binario - 1);
						aux <<= (8 - (7 - bits_unario - i));
						aux >>= (8 - (7 - bits_unario - i));
						bits_binario--;
					}
				}
				bits_unario = 0;
			}
		}
	}
	return parte_binaria;
}
/*Calcula la cantidad de bits disponibles en el último byte de la lista que
  compone al gamma pasado por parámetro (si hay mas de uno toma el primero)*/
unsigned char calcularBitsDisponibles(std::list<unsigned char*> *gamma)
{
	unsigned int bits_disponibles;
	unsigned int bits_gamma = (2 * calcularParteUnaria(gamma)) - 1;
	if(bits_gamma <= 8)
	{
		bits_disponibles = 8 - bits_gamma;
	}
	else
	{
		bits_disponibles = pow(2, ((unsigned int)((log10(bits_gamma) + DBL_EPSILON)/log10(2))) + 1) - bits_gamma;
		bits_disponibles = bits_disponibles % 8;
	}
	return bits_disponibles;
}
/*Calcula la cantidad de bits que ocupa el codigo gamma pasado 
  por parametro (si hay mas de uno, toma el primero)*/
unsigned char calcularBitsOcupados(std::list<unsigned char*> *gamma)
{
	unsigned char bits_unario = calcularParteUnaria(gamma);
	if(bits_unario == 0)
		return 0;
	return (2 * bits_unario) - 1;
}
//
unsigned int Gamma::getDistancia(std::list<unsigned char*> *gamma)
{
	unsigned char unario = calcularParteUnaria(gamma);
	unsigned int binario = calcularParteBinaria(gamma);
	unsigned int distancia = binario + pow(2, (unario -1));
	return distancia;
}
//
std::list<unsigned int*> *Gamma::getDistancias()
{
	//bits que ocupa el 1er gamma
	unsigned char bits_ocupados = calcularBitsOcupados(gammas);
	while(bits_ocupados > 0)
	{
		//lista auxiliar utilizada en el getDistancias
		std::list<unsigned char*> *aux = new std::list<unsigned char*>();
		//guardo el primer gamma en aux
		while(bits_ocupados > 0)
		{
			if(bits_ocupados >= 8)
			{
				aux->push_back(new unsigned char(*gammas->front()));
				delete(gammas->front());
				gammas->pop_front();
				bits_ocupados -=8;
			}
			else
			{
				unsigned char ultimo = *gammas->front();
				ultimo = ultimo >> (8-bits_ocupados);
				ultimo = ultimo << (8-bits_ocupados);
				aux->push_back(new unsigned char(ultimo));
				//desplazo todo bits_ocupados bits a la izquierda
				std::list<unsigned char*>::iterator it;
				for(it = gammas->begin(); it != gammas->end(); it++)
				{
					std::list<unsigned char*>::iterator it_aux = it;
					it++;
					if(it != gammas->end())
						**it_aux = (**it_aux << bits_ocupados) + (**it >> (8 - bits_ocupados));
					else
						**it_aux = (**it_aux << bits_ocupados) + pow(2,bits_ocupados) - 1;
					it = it_aux;
				}
				unsigned int *pepe = new unsigned int(getDistancia(aux));
				distancias->push_back(pepe);
				bits_ocupados = 0;
			}
		}
		std::list <unsigned char*>::iterator it3;
		for(it3 = aux->begin(); it3 != aux->end(); it3++)
			delete(*it3);
		delete(aux);
		//bits que ocupa el 1er gamma
		bits_ocupados = calcularBitsOcupados(gammas);
	}
	return distancias;
}
//Comprime la distancia pasada por parámetro, utilizando la codificación gamma
std::list<unsigned char*> *Gamma::getGamma(unsigned int distancia)
{
	unsigned int parte_unaria = 1 + ((log10(distancia) + FLT_EPSILON)/log10(2));
	std::list <unsigned char*> *gamma = getUnario(parte_unaria);
	unsigned int parte_binaria = distancia - pow(2, parte_unaria - 1);
	//cantidad de bits de la parte binaria
	unsigned int bits_parte_binaria_1 = 1 + ((log10(parte_binaria) + DBL_EPSILON)/log10(2));
	//cantidad de bits con la que se debe representar la parte binaria
	unsigned int bits_parte_binaria_2 = parte_unaria - 1; 
	//obtengo el ultimo byte de la parte unaria
	unsigned char ultimo = *gamma->back();
	//lo saco de la lista (luego lo vuelvo a ingresar junto con la parte binaria)
	delete(gamma->back());
	gamma->pop_back(); 
	//cantidad de bits disponibles en el ultimo byte de la parte unaria
	unsigned char bits_disponibles = calcularBitsDisponibles(ultimo);
	//Si tengo bits disponibles...
	if(bits_parte_binaria_2 == 0)
		gamma->push_back(new unsigned char(127));
	while(bits_parte_binaria_2 > 0)
	{
		if(bits_disponibles > 0)
		{
			if(bits_parte_binaria_2 == bits_disponibles)
			{	
				ultimo += parte_binaria;
				gamma->push_back(new unsigned char(ultimo));
				bits_parte_binaria_2 = 0;
			}
			else if(bits_parte_binaria_2 < bits_disponibles)
			{
				//parte_binaria representada en bits_disponibles bits
				parte_binaria = parte_binaria << (bits_disponibles - bits_parte_binaria_2);
				unsigned char relleno = pow(2,(bits_disponibles - bits_parte_binaria_2)) - 1;
				ultimo += parte_binaria + relleno;
				gamma->push_back(new unsigned char(ultimo));
				bits_parte_binaria_2 = 0;
			}
			else
			{
				unsigned int aux = parte_binaria;
				parte_binaria = parte_binaria >> (bits_parte_binaria_2 - bits_disponibles);
				ultimo += parte_binaria;
				gamma->push_back(new unsigned char(ultimo));
				parte_binaria = aux - (parte_binaria << (bits_parte_binaria_2 - bits_disponibles));
				bits_parte_binaria_2 -= bits_disponibles;
				bits_disponibles = 8;
				ultimo = 0;
			}
		}
		else
		{
			gamma->push_back(new unsigned char(ultimo));
			bits_disponibles = 8;
			ultimo = 0;
		}
	}
	return gamma;
}
//Devuelve el codigo unario de la distancia pasada por parámetro
std::list<unsigned char*> *Gamma::getUnario(unsigned int distancia)
{
	bool listo = false;
	std::list<unsigned char*> *unario = new std::list<unsigned char*>;
	unsigned int aux = distancia / 8;
	unsigned int resto = distancia % 8;
	for (unsigned int i = 0; i <= aux; i++)
	{
		if((resto == 0)&&(!listo))
		{
			i++;
			listo = true;
		}
		if(i == aux)
		{
			if(resto==0)
				resto=8;
			unario->push_back(new unsigned char(255<<9-resto));
		}
		else
		{
			unario->push_back(new unsigned char(255));
		}
	}
	return unario;
}
/*Método que comprime las distancias utlizando el codigo gamma y 
  las devuelve en una lista de unsigned char (bytes)*/
std::list<unsigned char*> *Gamma::getGammas()
{
	//Empiezo poniendo en la lista la primer distancia codificada en gamma
	gammas = getGamma(*distancias->front());
	std::list<unsigned int*>::iterator it;
	//Calculo la cantidad de bits disponibles
	unsigned int bits_disponibles = calcularBitsDisponibles(gammas);
	for(it = distancias->begin(); it != distancias->end(); it++)
	{
		if(it != distancias->begin())
		{
			std::list<unsigned char*> *actual = getGamma(**it);
			unsigned int bits_ocupados = calcularBitsOcupados(actual);
			std::list<unsigned char*>::iterator itActual;
			for(itActual = actual->begin(); itActual != actual->end(); itActual++)
			{ 
				if(bits_disponibles == 0)
				{
					gammas->push_back(new unsigned char(**itActual));
					if(bits_ocupados < 8)
					{
						std::list <unsigned char*> *gammaAux = getGamma(**it);
						bits_disponibles = calcularBitsDisponibles(gammaAux);
						std::list <unsigned char*>::iterator itt;
						for(itt = gammaAux->begin(); itt != gammaAux->end(); itt++)
							delete(*itt);
						delete(gammaAux);
					}
					bits_ocupados -= 8;
				}
				else if(bits_disponibles >= bits_ocupados)
				{
					unsigned char ultimo = *gammas->back();
					delete(gammas->back());
					gammas->pop_back();
					//Limpio los restos...
					ultimo = ultimo >> bits_disponibles;
					ultimo = ultimo << bits_disponibles;
					unsigned char aux = **itActual >> (8 - bits_ocupados);
					ultimo += aux << (bits_disponibles - bits_ocupados);
					
					std::list<unsigned int*>::iterator copia = it;
					it++;
					//si es la ultima distancia y ...
					if(it == distancias->end())
					{
						unsigned char relleno = pow(2,(bits_disponibles - bits_ocupados)) - 1;
						ultimo+=relleno;
					}
					it=copia;
					
					gammas->push_back(new unsigned char(ultimo));
					bits_disponibles -= bits_ocupados;
				}
				else
				{
					unsigned int actual_ocupados;
					if(bits_ocupados > 8)
						actual_ocupados = 8;
					else
						actual_ocupados = bits_ocupados;
					unsigned char ultimo = *gammas->back();
					delete(gammas->back());
					gammas->pop_back();
					//Limpio los restos...
					ultimo = ultimo >> bits_disponibles;
					ultimo = ultimo << bits_disponibles;					
					ultimo += **itActual >> (8 - bits_disponibles);
					gammas->push_back(new unsigned char(ultimo));
					ultimo = **itActual << 8 - (8 - bits_disponibles);

					std::list<unsigned int*>::iterator copia = it;
					it++;
					//si es la ultima distancia y ...
					if((it == distancias->end())&&(bits_ocupados<=8))
					{
						unsigned char relleno = pow(2,8 - (8 - bits_disponibles)) - 1;
						ultimo+=relleno;
					}
					it=copia;

					gammas->push_back(new unsigned char(ultimo));
					bits_disponibles = 8 - (actual_ocupados - bits_disponibles);
					bits_ocupados -= actual_ocupados;
				}
			}
			std::list<unsigned char*>::iterator itt;
			for(itt = actual->begin(); itt != actual->end(); itt++)
				delete(*itt);
			delete(actual);
		}
	}
	return gammas;
}
//Libera la memoria utilizada
Gamma::~Gamma()
{
	std::list <unsigned int*>::iterator it;
	for(it = distancias->begin(); it != distancias->end(); it++)
		delete(*it);
	delete (distancias);
	std::list <unsigned char*>::iterator it2;
	for(it2 = gammas->begin(); it2 != gammas->end(); it2++)
		delete(*it2);
	delete (gammas);
}
